# No copyright
import asyncio
import logging

class MM:
  def __init__(self, player, pair, config):
    self._player = player
    self._pair   = pair

    self._lastBuy   = ""
    self._lastSell  = ""
    self._lastPrice = None

    self._delta        = float(config["delta"])
    self._positiveCut  = float(config["+cut"])
    self._negativeCut  = float(config["-cut"])

  async def update(self):
    sales = self._player.assets[self._pair.names[0]]
    crncy = self._player.assets[self._pair.names[1]]
    price = self._pair.ticker.price

    lot = (sales.amount + crncy.amount / price) / 2
    pos = sales.amount / lot - 1

    # evaluate if cutting should be executed
    negCut = False
    posCut = False
    if self._lastPrice is not None:
      change  = (price - self._lastPrice) / self._lastPrice
      negCut  = pos >  0.5 and change < -abs(self._negativeCut)
      posCut  = pos < -0.5 and change >  abs(self._positiveCut)
    cutting = negCut or posCut

    # check if the past orders are active
    lastBuyAgreed  = (self._lastBuy is not None and
                      self._lastBuy not in self._player.orders)
    lastSellAgreed = (self._lastSell is not None and
                      self._lastSell not in self._player.orders)

    if not lastBuyAgreed and not lastSellAgreed and not cutting:
      return

    # cancel active orders
    cancel_orders = []
    if not lastBuyAgreed and self._lastBuy is not None:
      logging.info(f"cancel buy")
      cancel_orders.append(self._lastBuy)
    elif lastBuyAgreed:
      logging.info(f"agree buy")

    if not lastSellAgreed and self._lastSell is not None:
      logging.info(f"cancel sell")
      cancel_orders.append(self._lastSell)
    elif lastSellAgreed:
      logging.info(f"agree sell")

    if len(cancel_orders) != 0:
      await self._player.cancel(self._pair.names, cancel_orders)
    self._lastBuy  = None
    self._lastSell = None

    # execute cutting
    if cutting:
      if negCut:
        logging.info(f"-cut (change: {change}, pos: {pos} -> 0)")
      if posCut:
        logging.info(f"+cut (change: {change}, pos: {pos} -> 0)")
      self._lastBuy   = ""
      self._lastSell  = ""
      sell_amount = pos * lot
      if sell_amount > 0:
        await self._player.orderMarketSell(self._pair.names, amount=sell_amount)
      else:
        await self._player.orderMarketBuy(self._pair.names, amount=-buy_amount)
      return

    # execute new cycle
    self._lastPrice = None
    buy_price  = price * (1 - self._delta);
    sell_price = price * (1 + self._delta);

    buy_amount  = min(_quad( pos) * lot, crncy.amount / buy_price)
    sell_amount = min(_quad(-pos) * lot, sales.amount)
    order_unit  = pow(10, -sales.precision)

    logging.info(f"new cycle (pos: {pos}, price: {price}, buy: {buy_amount}, sell: {sell_amount})")

    async def buy():
      self._lastBuy = (
          None if order_unit > buy_amount else
          await self._player.orderLimitBuy(
              self._pair.names, amount = buy_amount, price = buy_price))

    async def sell():
      self._lastSell = (
          None if order_unit > sell_amount else
          await self._player.orderLimitSell(
              self._pair.names, amount = sell_amount, price = sell_price))

    await asyncio.gather(buy(), sell())
    self._lastPrice = price

# https://kijitora-2018.hatenablog.com/entry/2018/12/23/102913
def _quad(x):
  if x < -1:
    return 1
  if x <= 1:
    return -1/4 * (x+1)**2 + 1
  return 0

